今天的練習非常簡單,我們要利用 ng update
來改變 app.component.ts
裡, title
屬性的值。
雖然這個練習非常地陽春、簡單,但大家還是能夠透過此練習學習到:
ng update
的 Schematic ,且真正做出來。ng update
的 Schematic 的測試程式。筆者認為,只要這幾個關鍵點都有學會,其他的東西就都不是什麼大問題了。
想學更多?可以試著閱讀 Angular Components 的原始碼。
首先先在 /src/ng-update
底下新增一個 index.ts
:
import { Rule, Tree, SchematicContext } from '@angular-devkit/schematics';
export function updateToV020(_options): Rule {
return (_tree: Tree, _context: SchematicContext) => {
return _tree;
}
}
然後在 /src
底下新增一個 migration.json
:
{
"$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"migration020": {
"version": "0.2.0",
"description": "Updates to 0.2.0",
"factory": "./ng-update/index#updateToV020"
}
}
}
接著打開 package.json
,加入 ng-update
的設定:
{
"//": "略",
"ng-update": {
"migrations": "./src/migration.json"
}
}
基礎設定好之後,可以先來寫個基本測試,以確認我們的設定有正常運作。
測試的部份可以從之前寫過的 index_spec.ts
複製到 /src/ng-update
底下後,再把測試案例改成剩下這樣:
it('測試 ng update', async () => {
const tree = await runner.runExternalSchematicAsync('./src/migration.json', 'migration020', {}, appTree).toPromise();
expect(tree.files.length).toBeGreaterThan(0);
});
從上述程式碼中可以看出,之前原本都是使用 runSchematicAsync
來實際執行我們的 Schematic ,但在這裡筆者已經將其換成 runExternalSchematicAsync
。
這是因為 ng-update
要執行的是設定在 migration.json
裡的 Schematic,這個部分跟其它 Schematic 不一樣,所以我們必須改為使用 runExternalSchematicAsync
。
接著就可以執行 npm test
來驗證目前的設定是否正確:
基礎測試沒問題後,我們就可以先把要做到的功能寫成測試程式碼:
it('測試 ng update', async () => {
const tree = await runner.runExternalSchematicAsync('./src/migration.json', 'migration020', {}, appTree).toPromise();
const componentContent = tree.readContent('/projects/hello/src/app/app.component.ts');
expect(componentContent).toContain('title = \'Leo Chen\'');
});
這時如果我們再執行 npm test
來驗證結果的話,應該會出現以下畫面:
這是正常的,因為實作根本就還沒做,怎麼可能會通過測試?!
所以這時候,我們就只需要實作出能夠把這個測試案例從紅燈
變成綠燈
的程式碼就夠了,而這也是 TDD ( Test-driven development ,測試驅動開發) 的入門心法之一。
測試寫好之後,我們就可以打開同在 /src/ng-update
底下的 index.ts
,並將之前寫過的支援 Angular Project 的部份複製過來。
首先是好用的 readIntoSourceFile
:
function readIntoSourceFile(host: Tree, modulePath: string): ts.SourceFile {
const text = host.read(modulePath);
if (text === null) {
throw new SchematicsException(`File ${modulePath} does not exist.`);
}
const sourceText = text.toString('utf-8');
return ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
}
該引入的東西筆者就不再贅述囉!
接著是解析 Angular 專案的部份:
export function updateToV020(): Rule {
return (_tree: Tree, _context: SchematicContext) => {
const workspaceConfigBuffer = _tree.read('angular.json');
if ( !workspaceConfigBuffer ) {
throw new SchematicsException('Not an Angular CLI workspace');
}
const workspaceConfig = JSON.parse(workspaceConfigBuffer.toString());
const projectName = workspaceConfig.defaultProject;
const project = workspaceConfig.projects[projectName];
const defaultProjectPath = buildDefaultPath(project);
return _tree;
}
}
需要特別留意的是,
updateToV020
這裡沒有參數可以使用,所以就不用加_options
。
最後是實際實作的部份:
export function updateToV020(): Rule {
return (_tree: Tree, _context: SchematicContext) => {
// 略
// 把 app.component.ts 轉成 Typescript AST
const componentPath = `${defaultProjectPath}/app.component.ts`;
const componentSourceFile = readIntoSourceFile(_tree, componentPath);
// 試著找出 title 屬性
const classDeclaration = componentSourceFile.statements.find( node => ts.isClassDeclaration(node) )! as ts.ClassDeclaration;
const allProperties = classDeclaration.members.filter( node => ts.isPropertyDeclaration(node) )! as ts.PropertyDeclaration[];
const titleProperty = allProperties.find( node => node.name.getText() === 'title' );
// 如果有找到 title 屬性再修改程式碼
if ( titleProperty ) {
const initialLiteral = titleProperty.initializer as ts.StringLiteral;
const componentRecorder = _tree.beginUpdate(componentPath);
const startPos = initialLiteral.getStart();
componentRecorder.remove(startPos, initialLiteral.getWidth());
componentRecorder.insertRight(startPos, '\'Leo Chen\'');
_tree.commitUpdate(componentRecorder);
}
return _tree;
}
}
這邊需要留意的是,
initialLiteral.pos
跟initialLiteral.getStart()
所取得的位置不一樣,initialLiteral.getWidth()
與initialLiteral.getFullWidth()
取得的寬度也不一樣,請小心使用。
功能實作完成後,可以再輸入 npm test
驗證看看,應該要是可以把紅燈
變成綠燈
的噢!
結果:
除了下
npm test
之外,也可以到其他 Angular 專案裡,輸入schematics {relativePath}/src/migration.json:migration020 --debug=false
實際跑跑看!如果想用
ng upadte
驗證的話,就只能先將 Package 上傳之後才能測試。想實測看看的話,可以先使用筆者的 Package ,並按照以下步驟測試:
- 輸入
npm install angular-schematics-30days-challenge@0.1.0
。- 輸入
ng update angular-schematics-30days-challenge
。學習即玩樂,親自動手玩玩看吧!
今天的程式碼:https://github.com/leochen0818/angular-schematics-30days-challenge/tree/day16
ng update
所做的事情是真的稍微樸實無華且枯燥了一點,不過實際做完之後還是滿有成就感的!希望各位也能成功做出實際支援 ng update
的 Schematic 。
明天筆者將會分享如何打包、發佈自己的專案到 npm 上,敬請期待!